/* md5.c
**
** Copyright (c) 2007, Meadhbh S. Hamrick
** All rights reserved.
** 
** Redistribution  and  use  in  source  and  binary  forms,  with  or  without
** modification, are permitted provided that the following conditions are met:
** 
**  * Redistributions of  source code must  retain the above  copyright notice,
**    this list of conditions and the following disclaimer.
** 
**  * Redistributions in binary form must reproduce the above copyright notice,
**    this list of conditions and the following disclaimer in the documentation
**    and/or other materials provided with the distribution.
** 
**  * Neither the  name of  Project Meadhbh  nor the names of  its contributors
**    may be used  to endorse  or promote  products derived from this  software
**    without specific prior written permission.
** 
** THIS SOFTWARE IS PROVIDED BY  THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
** AND ANY  EXPRESS OR IMPLIED WARRANTIES,  INCLUDING, BUT NOT  LIMITED TO, THE
** IMPLIED WARRANTIES  OF MERCHANTABILITY AND FITNESS FOR  A PARTICULAR PURPOSE
** ARE DISCLAIMED.  IN NO  EVENT SHALL THE  COPYRIGHT OWNER OR  CONTRIBUTORS BE
** LIABLE  FOR  ANY  DIRECT,   INDIRECT,  INCIDENTAL,  SPECIAL,  EXEMPLARY,  OR
** CONSEQUENTIAL  DAMAGES  (INCLUDING,  BUT  NOT  LIMITED  TO,  PROCUREMENT  OF
** SUBSTITUTE GOODS  OR SERVICES;  LOSS OF USE,  DATA, OR PROFITS;  OR BUSINESS
** INTERRUPTION)  HOWEVER CAUSED  AND ON  ANY THEORY  OF LIABILITY,  WHETHER IN
** CONTRACT,  STRICT LIABILITY,  OR  TORT (INCLUDING  NEGLIGENCE OR  OTHERWISE)
** ARISING IN ANY WAY  OUT OF THE USE OF THIS SOFTWARE,  EVEN IF ADVISED OF THE
** POSSIBILITY OF SUCH DAMAGE.
** 
** $Id: md5.c 50 2007-08-31 08:11:28Z msh.mobile $
*/

/** \file md5.c
 ** This file implements the MD5 cryptographic hash function.
 */

/* Macro Definitions */

#ifndef MC_ROTATE_LEFT
#define MC_ROTATE_LEFT(x,n,b) (((x)<<(n))|((x)>>(b-n)))
#endif

#define MC_MIC_MD5_F(x,y,z) (((x)&(y))|((~x)&(z)))
#define MC_MIC_MD5_G(x,y,z) (((x)&(z))|((y)&(~(z))))
#define MC_MIC_MD5_H(x,y,z) ((x)^(y)^(z))
#define MC_MIC_MD5_I(x,y,z) ((y)^((x)|(~(z))))

#define MC_MIC_MD5_ROUND1(a,b,c,d,k,t,s) ((a)=((b)+MC_ROTATE_LEFT(((a)+MC_MIC_MD5_F((b),(c),(d))+(k)+(t)),(s),32)))
#define MC_MIC_MD5_ROUND2(a,b,c,d,k,t,s) ((a)=((b)+MC_ROTATE_LEFT(((a)+MC_MIC_MD5_G((b),(c),(d))+(k)+(t)),(s),32)))
#define MC_MIC_MD5_ROUND3(a,b,c,d,k,t,s) ((a)=((b)+MC_ROTATE_LEFT(((a)+MC_MIC_MD5_H((b),(c),(d))+(k)+(t)),(s),32)))
#define MC_MIC_MD5_ROUND4(a,b,c,d,k,t,s) ((a)=((b)+MC_ROTATE_LEFT(((a)+MC_MIC_MD5_I((b),(c),(d))+(k)+(t)),(s),32)))

/* File Includes */
#include "mutil.h"
#include "mcrypto/md5.h"

/* Static Prototypes */

static void mc_mic_md5_transform( tMCMd5 *context );

/* Static Variables */

void mc_mic_md5_clone( tMCMd5 *new_context, tMCMd5 *old_context ) {
  memcpy( new_context, old_context, sizeof( tMCMd5 ) );
}

/** \fn tMCErr mc_mic_md5_clear( tMCMd5 *context )
**  \brief clear sensitive data from the context
*/
void mc_mic_md5_clear( tMCMd5 *context ) {
  mc_mic_md5_initialize( context );
}

/** \fn tMCErr mc_mic_md5_initialize( tMCMd5 *context )
**  \brief initialize a MD5 context to it's initial condition
*/
void mc_mic_md5_initialize( tMCMd5 *context ) {
  context->count = 0;

  memset( context->data.asChar, 0, 64 );

  context->state[0] = 0x67452301;
  context->state[1] = 0xEFCDAB89;
  context->state[2] = 0x98BADCFE;
  context->state[3] = 0x10325476;
}
 
/** \fn tMCErr mc_mic_md5_update( tMCMd5 *context, uint8_t *data, uint32_t length )
**  \brief provide input to the hash function
*/

void mc_mic_md5_update( tMCMd5 *context, uint8_t *data, uint32_t length ) {
  uint32_t input_index;
  uint32_t block_index;

  for( input_index = 0; input_index < length; input_index ++ ) {

    /* copy data into the buffer */
    block_index = context->count % 64;
    context->data.asChar[ block_index ] = data[ input_index ];

    /* update the counter */
    context->count++;

    /* if the block buffer is full, transform the data */
    if( 63 == block_index ) {
      mc_mic_md5_transform( context );
    }
  }
}

/** \fn tMCErr mc_mic_md5_digest( tMCMd5 *context, uint8_t *digest )
**  \brief get the digest of the data provided
*/
void mc_mic_md5_digest( tMCMd5 *context, uint8_t *digest ) {
  uint32_t next_pad_index;
  uint32_t i;

  /* where does the first padding byte go? */
  next_pad_index = ( context->count % 64 );

  /* put the first padding byte */
  context->data.asChar[ next_pad_index++ ] = 0x80;

  /* are we past the 56th byte in the data buffer? */

  if( next_pad_index > 55 ) {
    mc_mic_md5_transform( context );
    next_pad_index = 0;
  }

  for( ; next_pad_index < 56; next_pad_index++ ) {
    context->data.asChar[ next_pad_index ] = 0;
  }

  /* append count to block */

#ifdef WORDS_BIGENDIAN
  context->data.asLongLong[ 7 ] = MU_END64(context->count * 8);
#else
  context->data.asLongLong[ 7 ] = context->count * 8;
#endif

  /* call transform */

  mc_mic_md5_transform( context );

  /* we may have to change state endianness */
#ifdef WORDS_BIGENDIAN
  for( i=0; i<4; i++ ) {
    context->state[i] = MU_END32( context->state[i] );
  }
#endif

  /* copy state to digest */
  memcpy( digest, (uint8_t *) context->state, 16 );

}

/** \fn void mc_mic_md5_transform( tMCMd5 *context )
**  \brief "churn" the internal state of the MD5 context based on input data
*/
static void mc_mic_md5_transform( tMCMd5 *context ) {
  uint32_t local_state[4];
  uint32_t i;

  /* save the state */
  local_state[0] = context->state[0];
  local_state[1] = context->state[1];
  local_state[2] = context->state[2];
  local_state[3] = context->state[3];

  /* depending on the endianness of the processor, we may have to change */
  /* the endianness of the data array                                    */
#ifdef WORDS_BIGENDIAN
  for( i = 0; i < 16; i++ ) {
    context->data.asInt[i] = ( uint32_t ) MU_END32( context->data.asInt[i] );
  }
#endif

  /* Round 1 */
  MC_MIC_MD5_ROUND1( 
    local_state[0], local_state[1], local_state[2], local_state[3],
    context->data.asInt[0], 0xD76AA478,  7 );
  MC_MIC_MD5_ROUND1( 
    local_state[3], local_state[0], local_state[1], local_state[2],
    context->data.asInt[1], 0xE8C7B756,  12 );
  MC_MIC_MD5_ROUND1( 
    local_state[2], local_state[3], local_state[0], local_state[1],
    context->data.asInt[2], 0x242070DB,  17 );
  MC_MIC_MD5_ROUND1( 
    local_state[1], local_state[2], local_state[3], local_state[0],
    context->data.asInt[3], 0xC1BDCEEE,  22 );
  MC_MIC_MD5_ROUND1( 
    local_state[0], local_state[1], local_state[2], local_state[3],
    context->data.asInt[4], 0xF57C0FAF,  7 );
  MC_MIC_MD5_ROUND1( 
    local_state[3], local_state[0], local_state[1], local_state[2],
    context->data.asInt[5], 0x4787C62A,  12 );
  MC_MIC_MD5_ROUND1( 
    local_state[2], local_state[3], local_state[0], local_state[1],
    context->data.asInt[6], 0xA8304613,  17 );
  MC_MIC_MD5_ROUND1( 
    local_state[1], local_state[2], local_state[3], local_state[0],
    context->data.asInt[7], 0xFD469501,  22 );
  MC_MIC_MD5_ROUND1( 
    local_state[0], local_state[1], local_state[2], local_state[3],
    context->data.asInt[8], 0x698098D8,  7 );
  MC_MIC_MD5_ROUND1( 
    local_state[3], local_state[0], local_state[1], local_state[2],
    context->data.asInt[9], 0x8B44F7AF,  12 );
  MC_MIC_MD5_ROUND1( 
    local_state[2], local_state[3], local_state[0], local_state[1],
    context->data.asInt[10], 0xFFFF5BB1,  17 );
  MC_MIC_MD5_ROUND1( 
    local_state[1], local_state[2], local_state[3], local_state[0],
    context->data.asInt[11], 0x895CD7BE,  22 );
  MC_MIC_MD5_ROUND1( 
    local_state[0], local_state[1], local_state[2], local_state[3],
    context->data.asInt[12], 0x6B901122,  7 );
  MC_MIC_MD5_ROUND1( 
    local_state[3], local_state[0], local_state[1], local_state[2],
    context->data.asInt[13], 0xFD987193,  12 );
  MC_MIC_MD5_ROUND1( 
    local_state[2], local_state[3], local_state[0], local_state[1],
    context->data.asInt[14], 0xA679438E,  17 );
  MC_MIC_MD5_ROUND1( 
    local_state[1], local_state[2], local_state[3], local_state[0],
    context->data.asInt[15], 0x49B40821,  22 );
  
  /* Round 2 */
  MC_MIC_MD5_ROUND2( 
    local_state[0], local_state[1], local_state[2], local_state[3],
    context->data.asInt[1], 0xF61E2562,  5 );
  MC_MIC_MD5_ROUND2( 
    local_state[3], local_state[0], local_state[1], local_state[2],
    context->data.asInt[6], 0xC040B340,  9 );
  MC_MIC_MD5_ROUND2( 
    local_state[2], local_state[3], local_state[0], local_state[1],
    context->data.asInt[11], 0x265E5A51,  14 );
  MC_MIC_MD5_ROUND2( 
    local_state[1], local_state[2], local_state[3], local_state[0],
    context->data.asInt[0], 0xE9B6C7AA,  20 );
  MC_MIC_MD5_ROUND2( 
    local_state[0], local_state[1], local_state[2], local_state[3],
    context->data.asInt[5], 0xD62F105D,  5 );
  MC_MIC_MD5_ROUND2( 
    local_state[3], local_state[0], local_state[1], local_state[2],
    context->data.asInt[10], 0x02441453,  9 );
  MC_MIC_MD5_ROUND2( 
    local_state[2], local_state[3], local_state[0], local_state[1],
    context->data.asInt[15], 0xD8A1E681,  14 );
  MC_MIC_MD5_ROUND2( 
    local_state[1], local_state[2], local_state[3], local_state[0],
    context->data.asInt[4], 0xE7D3FBC8,  20 );
  MC_MIC_MD5_ROUND2( 
    local_state[0], local_state[1], local_state[2], local_state[3],
    context->data.asInt[9], 0x21E1CDE6,  5 );
  MC_MIC_MD5_ROUND2( 
    local_state[3], local_state[0], local_state[1], local_state[2],
    context->data.asInt[14], 0xC33707D6,  9 );
  MC_MIC_MD5_ROUND2( 
    local_state[2], local_state[3], local_state[0], local_state[1],
    context->data.asInt[3], 0xF4D50D87,  14 );
  MC_MIC_MD5_ROUND2( 
    local_state[1], local_state[2], local_state[3], local_state[0],
    context->data.asInt[8], 0x455A14ED,  20 );
  MC_MIC_MD5_ROUND2( 
    local_state[0], local_state[1], local_state[2], local_state[3],
    context->data.asInt[13], 0xA9E3E905,  5 );
  MC_MIC_MD5_ROUND2( 
    local_state[3], local_state[0], local_state[1], local_state[2],
    context->data.asInt[2], 0xFCEFA3F8,  9 );
  MC_MIC_MD5_ROUND2( 
    local_state[2], local_state[3], local_state[0], local_state[1],
    context->data.asInt[7], 0x676F02D9,  14 );
  MC_MIC_MD5_ROUND2( 
    local_state[1], local_state[2], local_state[3], local_state[0],
    context->data.asInt[12], 0x8D2A4C8A,  20 );
  
  /* Round 3 */
  MC_MIC_MD5_ROUND3( 
    local_state[0], local_state[1], local_state[2], local_state[3],
    context->data.asInt[5], 0xFFFA3942,  4 );
  MC_MIC_MD5_ROUND3( 
    local_state[3], local_state[0], local_state[1], local_state[2],
    context->data.asInt[8], 0x8771F681,  11 );
  MC_MIC_MD5_ROUND3( 
    local_state[2], local_state[3], local_state[0], local_state[1],
    context->data.asInt[11], 0x6D9D6122,  16 );
  MC_MIC_MD5_ROUND3( 
    local_state[1], local_state[2], local_state[3], local_state[0],
    context->data.asInt[14], 0xFDE5380C,  23 );
  MC_MIC_MD5_ROUND3( 
    local_state[0], local_state[1], local_state[2], local_state[3],
    context->data.asInt[1], 0xA4BEEA44,  4 );
  MC_MIC_MD5_ROUND3( 
    local_state[3], local_state[0], local_state[1], local_state[2],
    context->data.asInt[4], 0x4BDECFA9,  11 );
  MC_MIC_MD5_ROUND3( 
    local_state[2], local_state[3], local_state[0], local_state[1],
    context->data.asInt[7], 0xF6BB4B60,  16 );
  MC_MIC_MD5_ROUND3( 
    local_state[1], local_state[2], local_state[3], local_state[0],
    context->data.asInt[10], 0xBEBFBC70,  23 );
  MC_MIC_MD5_ROUND3( 
    local_state[0], local_state[1], local_state[2], local_state[3],
    context->data.asInt[13], 0x289B7EC6,  4 );
  MC_MIC_MD5_ROUND3( 
    local_state[3], local_state[0], local_state[1], local_state[2],
    context->data.asInt[0], 0xEAA127FA,  11 );
  MC_MIC_MD5_ROUND3( 
    local_state[2], local_state[3], local_state[0], local_state[1],
    context->data.asInt[3], 0xD4EF3085,  16 );
  MC_MIC_MD5_ROUND3( 
    local_state[1], local_state[2], local_state[3], local_state[0],
    context->data.asInt[6], 0x04881D05,  23 );
  MC_MIC_MD5_ROUND3( 
    local_state[0], local_state[1], local_state[2], local_state[3],
    context->data.asInt[9], 0xD9D4D039,  4 );
  MC_MIC_MD5_ROUND3( 
    local_state[3], local_state[0], local_state[1], local_state[2],
    context->data.asInt[12], 0xE6DB99E5,  11 );
  MC_MIC_MD5_ROUND3( 
    local_state[2], local_state[3], local_state[0], local_state[1],
    context->data.asInt[15], 0x1FA27CF8,  16 );
  MC_MIC_MD5_ROUND3( 
    local_state[1], local_state[2], local_state[3], local_state[0],
    context->data.asInt[2], 0xC4AC5665,  23 );
  
  /* Round 4 */
  MC_MIC_MD5_ROUND4( 
    local_state[0], local_state[1], local_state[2], local_state[3],
    context->data.asInt[0], 0xF4292244,  6 );
  MC_MIC_MD5_ROUND4( 
    local_state[3], local_state[0], local_state[1], local_state[2],
    context->data.asInt[7], 0x432AFF97,  10 );
  MC_MIC_MD5_ROUND4( 
    local_state[2], local_state[3], local_state[0], local_state[1],
    context->data.asInt[14], 0xAB9423A7,  15 );
  MC_MIC_MD5_ROUND4( 
    local_state[1], local_state[2], local_state[3], local_state[0],
    context->data.asInt[5], 0xFC93A039,  21 );
  MC_MIC_MD5_ROUND4( 
    local_state[0], local_state[1], local_state[2], local_state[3],
    context->data.asInt[12], 0x655B59C3,  6 );
  MC_MIC_MD5_ROUND4( 
    local_state[3], local_state[0], local_state[1], local_state[2],
    context->data.asInt[3], 0x8F0CCC92,  10 );
  MC_MIC_MD5_ROUND4( 
    local_state[2], local_state[3], local_state[0], local_state[1],
    context->data.asInt[10], 0xFFEFF47D,  15 );
  MC_MIC_MD5_ROUND4( 
    local_state[1], local_state[2], local_state[3], local_state[0],
    context->data.asInt[1], 0x85845DD1,  21 );
  MC_MIC_MD5_ROUND4( 
    local_state[0], local_state[1], local_state[2], local_state[3],
    context->data.asInt[8], 0x6FA87E4F,  6 );
  MC_MIC_MD5_ROUND4( 
    local_state[3], local_state[0], local_state[1], local_state[2],
    context->data.asInt[15], 0xFE2CE6E0,  10 );
  MC_MIC_MD5_ROUND4( 
    local_state[2], local_state[3], local_state[0], local_state[1],
    context->data.asInt[6], 0xA3014314,  15 );
  MC_MIC_MD5_ROUND4( 
    local_state[1], local_state[2], local_state[3], local_state[0],
    context->data.asInt[13], 0x4E0811A1,  21 );
  MC_MIC_MD5_ROUND4( 
    local_state[0], local_state[1], local_state[2], local_state[3],
    context->data.asInt[4], 0xF7537E82,  6 );
  MC_MIC_MD5_ROUND4( 
    local_state[3], local_state[0], local_state[1], local_state[2],
    context->data.asInt[11], 0xBD3AF235,  10 );
  MC_MIC_MD5_ROUND4( 
    local_state[2], local_state[3], local_state[0], local_state[1],
    context->data.asInt[2], 0x2AD7D2BB,  15 );
  MC_MIC_MD5_ROUND4( 
    local_state[1], local_state[2], local_state[3], local_state[0],
    context->data.asInt[9], 0xEB86D391, 21 );

/* Function Definitions */

/** \fn tMCErr mc_mic_md5_clone( tMCMd5 *new_context,  21 );
  
  /* add the local_state back into the context state */
  context->state[ 0 ] += local_state[ 0 ];
  context->state[ 1 ] += local_state[ 1 ];
  context->state[ 2 ] += local_state[ 2 ];
  context->state[ 3 ] += local_state[ 3 ];

  memset( context->data.asChar, 0, 64 );
}
